﻿using System;
using System.Collections.Generic;
using System.Drawing;
using System.Globalization;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;
using System.Text;
using System.Windows.Forms;

namespace Cluster.LiveJournalLib
{
    public class LiveJournal
    {
        protected string defaultPictureUrl;
        protected bool fastServer; // Use fast server?
        protected FriendGroup[] friendGroups;
        protected FriendInfo[] friendOf;
        protected FriendInfo[] friends;
        protected string hPassword = ""; // MD5 password
        protected string[] journals;
        protected LiveJournalMenuItem[] liveJournalMenu;
        protected Dictionary<uint, string> moods;
        protected string name; // Real name
        protected PictureInfo[] pictures;
        protected string[] tags;
        protected string user = ""; // Username
        protected uint bytesInCount;
        public uint BytesInCount { get { return bytesInCount; }}
        protected uint bytesOutCount;
        public uint BytesOutCount { get { return bytesOutCount; } }

        public LiveJournal()
        {
            tags = new string[] {};
            pictures = new PictureInfo[] {};
            liveJournalMenu = new LiveJournalMenuItem[] {};
            moods = new Dictionary<uint, string>();
            journals = new string[] {};
            friendOf = new FriendInfo[] {};
            friends = new FriendInfo[] {};
            friendGroups = new FriendGroup[] {};
            defaultPictureUrl = null;
            name = null;
            fastServer = false;
            bytesInCount = 0;
            bytesOutCount = 0;
        }

        public virtual string User
        {
            set
            {
                user = value;
                fastServer = false;
            }
            get { return user; }
        }

        public virtual string Password
        {
            set { hPassword = GetMD5Hash(value); }
        }

        public virtual string HPassword
        {
            set { hPassword = value; }
        }

        public bool FastServer
        {
            get { return fastServer; }
            set { fastServer = value; }
        }

        public string Name
        {
            get { return name; }
        }

        public FriendGroup[] FriendGroups
        {
            get { return friendGroups; }
        }

        public FriendInfo[] Friends
        {
            get { return friends; }
        }

        public FriendInfo[] FriendOf
        {
            get { return friendOf; }
        }

        public string[] Journals
        {
            get { return journals; }
        }

        public Dictionary<uint, string> Moods
        {
            get { return moods; }
        }

        public LiveJournalMenuItem[] LiveJournalMenu
        {
            get { return liveJournalMenu; }
        }

        public PictureInfo[] Pictures
        {
            get { return pictures; }
        }

        public string DefaultPictureUrl
        {
            get { return defaultPictureUrl; }
        }

        public string[] Tags
        {
            get { return tags; }
        }

        public Dictionary<string, string> LjRawQueryReal(Dictionary<string, string> parameters)
        {
            var result = new Dictionary<string, string>();
            // Connecting to internets (for Windows Mobile)
            try
            {
                var request = WebRequest.Create("http://0.0.0.0");
                request.GetResponse();
            }
            catch
            {
            }
            var tcpClient = new TcpClient();
            //tcpClient.ReceiveTimeout = 30000; // In .NET Compact Framework applications, the ReceiveTimeout enumeration value is supported but reserved for future use.
            var host = Dns.GetHostEntry("www.livejournal.com");
            if (host.AddressList.Length == 0) throw new Exception("Can't connect to www.livejournal.com");
            var endpoint = new IPEndPoint(host.AddressList[0], 80);
            try
            {
                tcpClient.Connect(endpoint);
            }
            catch
            {
                throw new Exception("Can't connect to www.livejournal.com");
            }
            var data = BuildDataRequest(parameters);
            var databuf = Encoding.UTF8.GetBytes(data);
            var buf =
                "POST /interface/flat HTTP/1.0\r\nHost: www.livejournal.com\r\nContent-type: application/x-www-form-urlencoded\r\nContent-length: " +
                databuf.Length + "\r\n" +
                (fastServer ? "Cookie: ljfastserver=1\r\n" : "") +
                "\r\n" + data;
            tcpClient.Client.Send(Encoding.UTF8.GetBytes(buf));
            bytesOutCount += (uint)buf.Length;
            var b = new byte[1024];
            int i;
            var reply = new StringBuilder();
            var replyBuf = new List<byte>();
            while ((i = tcpClient.Client.Receive(b)) != 0)
            {                
                //reply.Append(Encoding.UTF8.GetString(b, 0, i));
                if (i > 0)
                {
                    bytesInCount += (uint)i;
                    for (uint c = 0; c < i; c++) replyBuf.Add(b[c]);
                }
                Application.DoEvents();
            }
            reply = new StringBuilder(Encoding.UTF8.GetString(replyBuf.ToArray(), 0, replyBuf.Count));
            reply.Replace("\r", "");
            var lines = reply.ToString().Split(new[] {'\n'});
            if (lines.Length == 0) throw new Exception("There is no reply from server");
            if (!lines[0].Contains("200 OK")) throw new Exception("Invalid reply: " + lines[0]);
            uint replyStart = 0;
            foreach (var line in lines)
            {
                replyStart++;
                if (line == "") break;
            }
            for (var lineNum = replyStart; lineNum + 1 < lines.Length; lineNum += 2)
                if (lines[lineNum] != "")
                {
                    //result.Add(DecodeHtmlChars(lines[lineNum]), DecodeHtmlChars(lines[lineNum + 1]));
                    result.Add(lines[lineNum], lines[lineNum + 1]);
                }
            return result;
        }
        public Dictionary<string, string> LjRawQuery(Dictionary<string, string> parameters)
        {
            // Try two times, lol! :)
            try
            {
                return LjRawQueryReal(parameters);
            }
            catch (LiveJournalException ex)
            {
                throw ex;
            }
            catch
            {
                return LjRawQueryReal(parameters);
            }
        }

        private static string BuildDataRequest(Dictionary<string, string> parameters)
        {
            var result = new StringBuilder();
            foreach (var key in parameters.Keys)
            {
                if (result.Length > 0) result.Append("&");
                result.Append(EncodeHtmlChars(key));
                result.Append("=");
                result.Append(EncodeHtmlChars(parameters[key]));
            }
            return result.ToString();
        }

        private static string EncodeHtmlChars(string text)
        {
            var result = new StringBuilder();
            var len = text.Length;
            for (var c = 0; c < len; c++)
            {
                var ch = text[c];
                if (((ch >= '0') && (ch <= '9')) || ((ch >= 'a') && (ch <= 'z')) || ((ch >= 'A') && (ch <= 'Z')))
                {
                    result.Append(ch);
                }
                else if (ch == ' ')
                {
                    result.Append("+");
                }
                else
                {
                    var bytes = Encoding.UTF8.GetBytes(new[] {ch});
                    foreach (var b in bytes) result.Append("%" + b.ToString("X2"));
                }
            }
            return result.ToString();
        }

        private static string DecodeHtmlChars(string text)
        {
            var len = text.Length;
            var resBuf = new List<byte>();
            for (var c = 0; c < len; c++)
            {
                var ch = text[c];
                if (ch == '%')
                {
                    var b = byte.Parse(text.Substring(c + 1, 2), NumberStyles.HexNumber);
                    resBuf.Add(b);
                    c += 2;
                }
                else if (ch == '+')
                    resBuf.Add(32);
                else
                {
                    var bytes = Encoding.UTF8.GetBytes(new[] {ch});
                    foreach (var b in bytes) resBuf.Add(b);
                }
            }
            return Encoding.UTF8.GetString(resBuf.ToArray(), 0, resBuf.Count);
        }

        public static string GetMD5Hash(string input)
        {
            var x = new MD5CryptoServiceProvider();
            var bs = Encoding.UTF8.GetBytes(input);
            bs = x.ComputeHash(bs);
            var s = new StringBuilder();
            foreach (var b in bs)
            {
                s.Append(b.ToString("x2").ToLower());
            }
            var password = s.ToString();
            return password;
        }

        protected virtual LiveJournalMenuItem[] ParseMenu(IDictionary<string, string> parameters, uint menuId)
        {
            var menu = new List<LiveJournalMenuItem>();
            string result;
            if (parameters.TryGetValue("menu_" + menuId + "_count", out result))
            {
                var menuCount = uint.Parse(result);
                for (uint c = 1; c <= menuCount; c++)
                {
                    var menuItem = new LiveJournalMenuItem();
                    parameters.TryGetValue("menu_" + menuId + "_" + c + "_text", out menuItem.text);
                    parameters.TryGetValue("menu_" + menuId + "_" + c + "_url", out menuItem.url);
                    if (parameters.TryGetValue("menu_" + menuId + "_" + c + "_sub", out result))
                        menuItem.submenu = ParseMenu(parameters, uint.Parse(result));
                    menu.Add(menuItem);
                }
            }
            return menu.ToArray();
        }

        public uint? GetMoodId(string mood)
        {
            foreach (var id in Moods.Keys)
                if (Moods[id] == mood) return id;
            return null;
        }

        public uint? GetFriendGroupId(string groupName)
        {
            foreach (var friendGroup in friendGroups)
                if (friendGroup.name == groupName) return friendGroup.id;
            return null;
        } // Returns friend group ID
        public uint GetFriendGroupMask(string[] groups)
        {
            uint result = 0;
            foreach (var group in groups)
            {
                var id = GetFriendGroupId(group);
                if (id != null)
                {
                    var readId = (int) (id);
                    result += (uint) (1 << readId);
                }
            }
            return result;
        } // Calculates friend groups mask
        protected virtual FriendInfo[] ParseFriendOf(IDictionary<string, string> answer)
        {
            uint friendCount = 0;
            var friendsList = new List<FriendInfo>();
            string result;
            if (answer.TryGetValue("friendof_count", out result)) friendCount = uint.Parse(result);
            for (var c = 1; c <= friendCount; c++)
            {
                var friend = new FriendInfo();
                answer.TryGetValue("friendof_" + c + "_user", out friend.userName);
                answer.TryGetValue("friendof_" + c + "_name", out friend.name);
                if (answer.TryGetValue("friendof_" + c + "_bg", out result))
                    friend.backgroundColor = Color.FromArgb(int.Parse(result.Remove(0, 1), NumberStyles.HexNumber));
                if (answer.TryGetValue("friendof_" + c + "_fg", out result))
                    friend.foregroundColor = Color.FromArgb(int.Parse(result.Remove(0, 1), NumberStyles.HexNumber));
                answer.TryGetValue("friendof_" + c + "_identity_display", out friend.identityDisplay);
                answer.TryGetValue("friendof_" + c + "_identity_type", out friend.identityType);
                answer.TryGetValue("friendof_" + c + "_identity_value", out friend.identityValue);
                if (answer.TryGetValue("friendof_" + c + "_type", out result))
                {
                    switch (result)
                    {
                        case "community":
                            friend.friendType = FriendType.Community;
                            break;
                        case "news":
                            friend.friendType = FriendType.News;
                            break;
                        case "shared":
                            friend.friendType = FriendType.Shared;
                            break;
                        case "identity":
                            friend.friendType = FriendType.Identity;
                            break;
                    }
                }
                else friend.friendType = FriendType.Normal;
                if (answer.TryGetValue("friendof_" + c + "_status", out result))
                {
                    switch (result)
                    {
                        case "deleted":
                            friend.friendStatus = FriendStatus.Deleted;
                            break;
                        case "suspended":
                            friend.friendStatus = FriendStatus.Suspended;
                            break;
                        case "purged":
                            friend.friendStatus = FriendStatus.Purged;
                            break;
                    }
                }
                else friend.friendStatus = FriendStatus.Normal;
                friendsList.Add(friend);
            }

            return friendsList.ToArray();
        }

        protected virtual FriendGroup[] ParseFriendGroups(IDictionary<string, string> answer)
        {
            uint fgrpCount = 0;
            var friendGroupsList = new List<FriendGroup>();
            string result;
            if (answer.TryGetValue("frgrp_maxnum", out result)) fgrpCount = uint.Parse(result);
            for (uint c = 1; c <= fgrpCount; c++)
            {
                var friendGroup = new FriendGroup {id = c};
                string sortStr;
                if (
                    answer.TryGetValue("frgrp_" + c + "_name", out friendGroup.name) &&
                    answer.TryGetValue("frgrp_" + c + "_sortorder", out sortStr)
                    )
                {
                    friendGroup.sortorder = uint.Parse(sortStr);
                    friendGroup.isPublic = (answer.TryGetValue("frgrp_" + c + "_public", out result) && (result == "1"));
                    friendGroupsList.Add(friendGroup);
                }
            }
            return friendGroupsList.ToArray();
        }

        /// <summary>Clears all info about user. Use Login() to reload it.</summary>
        public void Clear()
        {
            fastServer = false;
            name = null;
            friendGroups = new FriendGroup[] {};
            journals = new string[] {};
            moods.Clear();
            liveJournalMenu = new LiveJournalMenuItem[] {};
            pictures = new PictureInfo[] {};
            defaultPictureUrl = null;
        }

        /// <summary>Mode that clients can use to poll the server to see if their Friends list has been updated.</summary>
        public CheckFriendsResult CheckFriends(DateTime? lastUpdate, uint? groupMask)
        {
            var checkFriendsResult = new CheckFriendsResult();
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "checkfriends";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (lastUpdate != null)
                parameters["lastupdate"] = ((DateTime) lastUpdate).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss");
            if (groupMask != null) parameters["mask"] = groupMask.ToString();
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            checkFriendsResult.newStuff = (answer.TryGetValue("new", out result) && (result == "1"));
            if (answer.TryGetValue("lastupdate", out result))
                checkFriendsResult.lastupdate =
                    DateTime.ParseExact(result, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLocalTime();
            if (answer.TryGetValue("uinterval", out result))
                checkFriendsResult.uinterval = uint.Parse(result);
            return checkFriendsResult;
        }

        /// <summary>Run an administrative command.</summary>
        public string[] ConsoleCommand(string command)
        {
            var parameters = new Dictionary<string, string>();
            uint lineCount = 0;
            var resultLines = new List<string>();
            parameters["mode"] = "consolecommand";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            parameters["command"] = command;
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            if (answer.TryGetValue("cmd_line_count", out result)) lineCount = uint.Parse(result);
            for (uint c = 1; c <= lineCount; c++)
            {
                if (answer.TryGetValue("cmd_line_" + c, out result)) resultLines.Add(result);
            }
            return resultLines.ToArray();
        }

        /// <summary>.Edit or delete a user's past journal entry</summary>
        public PostResult EditEvent(Event eventInfo, string useJournal)
        {
            return EditEvent(eventInfo.itemId, eventInfo.subject, eventInfo.eventText, eventInfo.security,
                             eventInfo.groupMask, eventInfo.datetime, useJournal, eventInfo.postOptions);
        }

        public PostResult EditEvent(int? itemId, string subject, string eventText, Security security, uint groupMask,
                                    DateTime? datetime, string useJournal, PostOptions? postOptions)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "editevent";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (itemId != null) parameters["itemid"] = itemId.ToString();
            parameters["subject"] = subject;
            if (eventText != null) parameters["event"] = eventText;
            switch (security)
            {
                case Security.Public:
                    parameters["security"] = "public";
                    break;
                case Security.Private:
                    parameters["security"] = "private";
                    break;
                case Security.UseMask:
                    parameters["security"] = "usemask";
                    parameters["allowmask"] = groupMask.ToString();
                    break;
            }
            parameters["year"] = (datetime ?? DateTime.Now).Year.ToString();
            parameters["mon"] = (datetime ?? DateTime.Now).Month.ToString();
            parameters["day"] = (datetime ?? DateTime.Now).Day.ToString();
            parameters["hour"] = (datetime ?? DateTime.Now).Hour.ToString();
            parameters["min"] = (datetime ?? DateTime.Now).Minute.ToString();
            if (useJournal != null) parameters["usejournal"] = useJournal;
            // Many other options...
            if (postOptions != null)
            {
                var options = (PostOptions) postOptions;
                options.writeRawData();
                foreach (string optName in options.rawProps.Keys) parameters[optName] = options.rawProps[optName];
            }

            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            var postResult = new PostResult();
            if (answer.TryGetValue("itemid", out result)) postResult.itemId = int.Parse(result);
            if (answer.TryGetValue("anum", out result)) postResult.anum = uint.Parse(result);
            answer.TryGetValue("url", out postResult.url);
            return postResult;
        }

        /// <summary>Edit the user's defined groups of friends.</summary>
        public void EditFriendGroups(FriendGroupAction[] friendGroupActions)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "editfriendgroups";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            foreach (var action in friendGroupActions)
            {
                if (action is FriendGroupEditMask)
                {
                    var data = action as FriendGroupEditMask;
                    parameters["editfriend_groupmask_" + data.FriendName] = data.Mask.ToString();
                }
                else if (action is FriendGroupDelete)
                {
                    var data = action as FriendGroupDelete;
                    parameters["efg_delete_" + data.FriendGroupId] = "1";
                }
                else if (action is FriendGroupSetName)
                {
                    var data = action as FriendGroupSetName;
                    parameters["efg_set_" + data.FriendGroupId + "_name"] = data.FriendGroupName;
                }
                else if (action is FriendGroupSetSort)
                {
                    var data = action as FriendGroupSetSort;
                    parameters["efg_set_" + data.FriendGroupId + "_sort"] = data.Sort.ToString();
                }
                else if (action is FriendGroupSetPublic)
                {
                    var data = action as FriendGroupSetPublic;
                    parameters["efg_set_" + data.FriendGroupId + "_public"] = (data.Public ? "1" : "0");
                }
            }
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
        }

        /// <summary>Download parts of the user's journal.</summary>
        public Event[] GetEvents(uint? truncate, bool preferSubject, bool noProps, SelectType selectType,
                                 DateTime? datetime, uint howMany, int itemId, string useJournal)
        {
            var events = new List<Event>();
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "getevents";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (truncate != null) parameters["truncate"] = truncate.ToString();
            if (preferSubject) parameters["prefersubject"] = "1";
            if (noProps) parameters["noprops"] = "1";
            switch (selectType)
            {
                case SelectType.Day:
                    parameters["selecttype"] = "day";
                    parameters["year"] = (datetime ?? DateTime.Now).Year.ToString();
                    parameters["month"] = (datetime ?? DateTime.Now).Month.ToString();
                    parameters["day"] = (datetime ?? DateTime.Now).Day.ToString();
                    break;
                case SelectType.LastN:
                    parameters["selecttype"] = "lastn";
                    parameters["howmany"] = howMany.ToString();
                    if (datetime != null)
                        parameters["beforedate"] = ((DateTime) datetime).ToString("yyyy-MM-dd HH:mm:ss");
                    break;
                case SelectType.One:
                    parameters["selecttype"] = "one";
                    parameters["itemid"] = itemId.ToString();
                    break;
                case SelectType.SyncItems:
                    parameters["selecttype"] = "syncitems";
                    parameters["lastsync"] = (datetime ?? DateTime.Now).ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss");
                    break;
            }
            if (useJournal != null) parameters["usejournal"] = useJournal;
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }

            uint eventsCount = 0;
            uint propCount = 0;
            if (answer.TryGetValue("events_count", out result)) eventsCount = uint.Parse(result);
            for (uint c = 1; c <= eventsCount; c++)
            {
                var ljEvent = new Event();
                if (answer.TryGetValue("events_" + c + "_itemid", out result)) ljEvent.itemId = int.Parse(result);
                if (answer.TryGetValue("events_" + c + "_eventtime", out result))
                    ljEvent.datetime = DateTime.ParseExact(result, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture);
                answer.TryGetValue("events_" + c + "_event", out ljEvent.eventText);
                ljEvent.eventText = DecodeHtmlChars(ljEvent.eventText);
                if (answer.TryGetValue("events_" + c + "_security", out result))
                {
                    switch (result)
                    {
                        case "public":
                            ljEvent.security = Security.Public;
                            break;
                        case "private":
                            ljEvent.security = Security.Private;
                            break;
                        case "usemask":
                            ljEvent.security = Security.UseMask;
                            break;
                        default:
                            ljEvent.security = Security.Public;
                            break;
                    }
                }
                else ljEvent.security = Security.Public;
                if (answer.TryGetValue("events_" + c + "_allowmask", out result))
                    ljEvent.groupMask = uint.Parse(result);
                answer.TryGetValue("events_" + c + "_subject", out ljEvent.subject);
                answer.TryGetValue("events_" + c + "_poster", out ljEvent.poster);
                if (answer.TryGetValue("events_" + c + "_anum", out result)) ljEvent.anum = uint.Parse(result);
                answer.TryGetValue("events_" + c + "_url", out ljEvent.url);
                events.Add(ljEvent);
            }
            if (answer.TryGetValue("prop_count", out result)) propCount = uint.Parse(result);
            for (uint propNum = 1; propNum <= propCount; propNum++)
            {
                uint propItemId = 0;
                if (answer.TryGetValue("prop_" + propNum + "_itemid", out result)) propItemId = uint.Parse(result);
                for (var eventNum = 0; eventNum < events.Count; eventNum++)
                    if (events[eventNum].itemId == propItemId)
                    {
                        string propName;
                        string propValue;
                        answer.TryGetValue("prop_" + propNum + "_name", out propName);
                        answer.TryGetValue("prop_" + propNum + "_value", out propValue);
                        var propEvent = events[eventNum];
                        if (propEvent.postOptions.rawProps == null) propEvent.postOptions.rawProps = new Dictionary<string, string>();
                        if (!string.IsNullOrEmpty(propName) && !string.IsNullOrEmpty(propValue))
                            propEvent.postOptions.rawProps[propName] = propValue;
                        events[eventNum] = propEvent;
                        break;
                    }
            }
            for (int c = 0; c < events.Count; c++)
            {
                var ljEvent = events[c];
                ljEvent.postOptions.parseRawData();
                events[c] = ljEvent;

            }
            return events.ToArray();
        }

        /// <summary>Retrieves a list of the user's defined groups of friends.</summary>
        public void LoadFriendGroups()
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "getfriendgroups";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            friendGroups = ParseFriendGroups(answer);
        }

        /// <summary>Returns a list of which other LiveJournal users list this user as their friend.</summary>
        public void LoadFriendOf()
        {
            LoadFriendOf(null);
        }

        /// <summary>Returns a list of which other LiveJournal users list this user as their friend.</summary>
        public void LoadFriendOf(uint? limit)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "friendof";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (limit != null) parameters["friendoflimit"] = limit.ToString();
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            friendOf = ParseFriendOf(answer);
        }

        /// <summary>Returns a list of which other LiveJournal users this user lists as their friend.</summary>
        public virtual void LoadFriends(bool includeFriendOf, bool includeGroups, bool includeBirthdays, uint? limit)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "getfriends";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (includeFriendOf) parameters["includefriendof"] = "1";
            if (includeGroups) parameters["includegroups"] = "1";
            if (includeBirthdays) parameters["includebdays"] = "1";
            if (limit != null) parameters["friendlimit"] = limit.ToString();
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }

            uint friendCount = 0;
            var friendsList = new List<FriendInfo>();
            if (answer.TryGetValue("friend_count", out result)) friendCount = uint.Parse(result);
            for (var c = 1; c <= friendCount; c++)
            {
                var friend = new FriendInfo();
                answer.TryGetValue("friend_" + c + "_user", out friend.userName);
                answer.TryGetValue("friend_" + c + "_name", out friend.name);
                answer.TryGetValue("friend_" + c + "_birthday", out friend.birthday);
                if (answer.TryGetValue("friend_" + c + "_bg", out result))
                    friend.backgroundColor = Color.FromArgb(int.Parse(result.Remove(0, 1), NumberStyles.HexNumber));
                if (answer.TryGetValue("friend_" + c + "_fg", out result))
                    friend.foregroundColor = Color.FromArgb(int.Parse(result.Remove(0, 1), NumberStyles.HexNumber));
                if (answer.TryGetValue("friend_" + c + "_groupmask", out result))
                    friend.groupMask = uint.Parse(result);
                answer.TryGetValue("friend_" + c + "_identity_display", out friend.identityDisplay);
                answer.TryGetValue("friend_" + c + "_identity_type", out friend.identityType);
                answer.TryGetValue("friend_" + c + "_identity_value", out friend.identityValue);
                if (answer.TryGetValue("friend_" + c + "_type", out result))
                {
                    switch (result)
                    {
                        case "community":
                            friend.friendType = FriendType.Community;
                            break;
                        case "news":
                            friend.friendType = FriendType.News;
                            break;
                        case "shared":
                            friend.friendType = FriendType.Shared;
                            break;
                        case "identity":
                            friend.friendType = FriendType.Identity;
                            break;
                    }
                }
                else friend.friendType = FriendType.Normal;
                if (answer.TryGetValue("friend_" + c + "_status", out result))
                {
                    switch (result)
                    {
                        case "deleted":
                            friend.friendStatus = FriendStatus.Deleted;
                            break;
                        case "suspended":
                            friend.friendStatus = FriendStatus.Suspended;
                            break;
                        case "purged":
                            friend.friendStatus = FriendStatus.Purged;
                            break;
                    }
                }
                else friend.friendStatus = FriendStatus.Normal;
                friendsList.Add(friend);
            }
            friends = friendsList.ToArray();
            if (includeFriendOf) friendOf = ParseFriendOf(answer);
            if (includeGroups) friendGroups = ParseFriendGroups(answer);
        }

        /// <summary>Adds friend.</summary>
        public void AddFriend(string userName, Color? foregroundColor, Color? backgroundColor, uint? groupMask)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "editfriends";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            parameters["editfriend_add_1_user"] = userName;
            if (foregroundColor != null)
                parameters["editfriend_add_1_fg"] = "#" + (((Color) foregroundColor).ToArgb() & 0xFFFFFF).ToString("X6");
            if (backgroundColor != null)
                parameters["editfriend_add_1_bg"] = "#" + (((Color) backgroundColor).ToArgb() & 0xFFFFFF).ToString("X6");
            if (groupMask != null) parameters["editfriend_add_1_groupmask"] = groupMask.ToString();
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
        }

        /// <summary>Removes friend.</summary>
        public void DeleteFriend(string userName)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "editfriends";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            parameters["editfriend_delete_" + userName] = "1";
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
        }

        /// <summary>Retrieves the number of journal entries per day. Useful for populating calendar widgets in GUI clients.</summary>
        public DayCount[] GetDayCounts(string useJournal)
        {
            var dayCounts = new List<DayCount>();
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "getdaycounts";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (useJournal != null) parameters["usejournal"] = useJournal;
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            foreach (var dateStr in answer.Keys)
            {
                try
                {
                    var dayCount = new DayCount
                    {
                        date = DateTime.ParseExact(dateStr, "yyyy-MM-dd", CultureInfo.InvariantCulture),
                        count = uint.Parse(answer[dateStr])
                    };
                    dayCounts.Add(dayCount);
                }
                catch
                {
                }
            }
            return dayCounts.ToArray();
        }

        public SyncInfo[] GetSyncItems(string useJournal, DateTime datetime)
        {
            return GetSyncItems(useJournal, datetime, new List<SyncInfo>());
        }

        protected SyncInfo[] GetSyncItems(string useJournal, DateTime datetime, List<SyncInfo> syncData)
        {            
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "syncitems";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (useJournal != null) parameters["usejournal"] = useJournal;
            parameters["lastsync"] = datetime.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss");
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }            
            
            uint syncTotal = 0;
            uint syncCount = 0;
            DateTime maxDatetime = datetime;
            if (answer.TryGetValue("sync_total", out result)) syncTotal = uint.Parse(result);
            if (answer.TryGetValue("sync_count", out result)) syncCount = uint.Parse(result);
            for (var c = 1; c <= syncCount; c++)
            {
                var syncInfo = new SyncInfo();
                if (answer.TryGetValue("sync_" + c + "_item", out result))
                {
                    var item = result.Split(new char[] { '-' });
                    syncInfo.type = item[0];
                    syncInfo.itemId = int.Parse(item[1]);
                    if (answer.TryGetValue("sync_" + c + "_time", out result))
                    {
                        syncInfo.datetime =
                            DateTime.ParseExact(result, "yyyy-MM-dd HH:mm:ss", CultureInfo.InvariantCulture).ToLocalTime();
                        if (syncInfo.datetime > maxDatetime) maxDatetime = syncInfo.datetime;
                    }
                    answer.TryGetValue("sync_" + c + "_action", out syncInfo.action);
                    syncData.Add(syncInfo);
                }
            }
            if (syncTotal > syncCount) GetSyncItems(useJournal, maxDatetime, syncData);
            return syncData.ToArray();
        }

        /// <summary>Retrieves a list of the user's defined tags.</summary>
        public virtual void LoadTags()
        {
            tags = GetTags(null);
        }
        /// <summary>Retrieves a list of the user's defined tags.</summary>
        public virtual string[] GetTags(string useJournal)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "getusertags";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (useJournal != null) parameters["usejournal"] = useJournal;
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            uint tagCount = 0;
            var tagList = new List<string>();
            if (answer.TryGetValue("tag_count", out result)) tagCount = uint.Parse(result);
            for (var c = 1; c <= tagCount; c++)
            {
                if (answer.TryGetValue("tag_" + c + "_name", out result))
                    tagList.Add(result);
            }
            return tagList.ToArray();
        }

        /// <summary>The most important mode, this is how a user actually submits a new log entry to the server.</summary>
        public PostResult PostEvent(string subject, string eventText,
                                    Security security, uint groupMask, DateTime? datetime,
                                    string useJournal, PostOptions? postOptions)
        {
            var parameters = new Dictionary<string, string>();
            parameters["mode"] = "postevent";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            parameters["subject"] = subject;
            parameters["event"] = eventText;
            switch (security)
            {
                case Security.Public:
                    parameters["security"] = "public";
                    break;
                case Security.Private:
                    parameters["security"] = "private";
                    break;
                case Security.UseMask:
                    parameters["security"] = "usemask";
                    parameters["allowmask"] = groupMask.ToString();
                    break;
            }
            parameters["year"] = (datetime ?? DateTime.Now).Year.ToString();
            parameters["mon"] = (datetime ?? DateTime.Now).Month.ToString();
            parameters["day"] = (datetime ?? DateTime.Now).Day.ToString();
            parameters["hour"] = (datetime ?? DateTime.Now).Hour.ToString();
            parameters["min"] = (datetime ?? DateTime.Now).Minute.ToString();
            if (useJournal != null) parameters["usejournal"] = useJournal;
            // Many other options...
            if (postOptions != null)
            {
                var options = (PostOptions)postOptions;
                options.writeRawData();
                foreach (string optName in options.rawProps.Keys) parameters[optName] = options.rawProps[optName];
            }
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            var postResult = new PostResult();
            if (answer.TryGetValue("itemid", out result)) postResult.itemId = int.Parse(result);
            if (answer.TryGetValue("anum", out result)) postResult.anum = uint.Parse(result);
            answer.TryGetValue("url", out postResult.url);
            return postResult;
        }

        /// <summary>The most important mode, this is how a user actually submits a new log entry to the server.</summary>
        public PostResult PostEvent(Event eventInfo, string useJournal)
        {
            return PostEvent(eventInfo.subject, eventInfo.eventText, eventInfo.security, eventInfo.groupMask,
                             eventInfo.datetime, useJournal, eventInfo.postOptions);
        }

        /// <summary>Log in to the server, while announcing your client version. The server returns with whether the password is good or not, the user's name, and the list of the user's friend groups. Method will return an optional message to be displayed to the user.</summary>
        public virtual string Login(bool getmoods, bool getmenus, bool getpickws, bool getpickwurls)
        {
            if (getpickwurls) getpickws = true;
            var parameters = new Dictionary<string, string>();
            string message;
            uint journalCount = 0;
            uint moodCount = 0;
            uint picCount = 0;
            parameters["mode"] = "login";
            parameters["ver"] = "1";
            parameters["user"] = user;
            parameters["hpassword"] = hPassword;
            if (getmoods) parameters["getmoods"] = "1";
            if (getmenus) parameters["getmenus"] = "1";
            if (getpickws) parameters["getpickws"] = "1";
            if (getpickwurls) parameters["getpickwurls"] = "1";
            fastServer = false;
            var answer = LjRawQuery(parameters);
            string result;
            if (!answer.TryGetValue("success", out result) || (result != "OK"))
            {
                if (answer.TryGetValue("errmsg", out result)) throw new LiveJournalException(result);
                throw new LiveJournalException("Unknown error");
            }
            Clear();
            answer.TryGetValue("name", out name);
            answer.TryGetValue("message", out message);
            friendGroups = ParseFriendGroups(answer);
            // Shared journals this user has access to post to
            if (answer.TryGetValue("access_count", out result)) journalCount = uint.Parse(result);
            var journalsList = new List<string>();
            for (uint c = 1; c <= journalCount; c++)
                if (answer.TryGetValue("access_" + c, out result)) journalsList.Add(result);
            journals = journalsList.ToArray();
            if (answer.TryGetValue("mood_count", out result)) moodCount = uint.Parse(result);
            for (uint c = 1; c <= moodCount; c++)
            {
                string id;
                string moodName;
                if (answer.TryGetValue("mood_" + c + "_id", out id) &&
                    answer.TryGetValue("mood_" + c + "_name", out moodName)
                    ) moods[uint.Parse(id)] = moodName;
            }
            liveJournalMenu = ParseMenu(answer, 0);
            if (answer.TryGetValue("pickw_count", out result)) picCount = uint.Parse(result);
            var picturesList = new List<PictureInfo>();
            for (uint c = 1; c <= picCount; c++)
            {
                var pictureInfo = new PictureInfo();
                answer.TryGetValue("pickw_" + c, out pictureInfo.keyword);
                answer.TryGetValue("pickwurl_" + c, out pictureInfo.url);
                picturesList.Add(pictureInfo);
            }
            pictures = picturesList.ToArray();
            answer.TryGetValue("defaultpicurl", out defaultPictureUrl);
            fastServer = (answer.TryGetValue("fastserver", out result) && (result == "1"));
            return message;
        }
    }

    public struct Event
    {
        public uint? anum;
        public DateTime datetime;
        public string eventText;
        public uint groupMask;
        public int? itemId;
        public string poster;
        public PostOptions postOptions;
        public Security security;
        public string subject;
        public string url;
    }

    public struct FriendGroup
    {
        public uint id;
        public bool isPublic;
        public string name;
        public uint sortorder;
    }

    public struct LiveJournalMenuItem
    {
        public LiveJournalMenuItem[] submenu;
        public string text;
        public string url;
    }

    public struct PictureInfo
    {
        public string keyword;
        public string url;
        public string fileName; // reserved
        public Image image; // reserved
    }

    public struct CheckFriendsResult
    {
        public DateTime lastupdate;
        public bool newStuff;
        public uint uinterval;
    }

    public struct PostResult
    {
        public uint anum;
        public int itemId;
        public string url;
    }

    public struct PostOptions
    {
        public Dictionary<string, string> rawProps;
        public string admin_content_flag;
        public AdultContent? adult_content;
        public uint? commentalter;
        public string current_coords;
        public string current_location;
        public string current_mood;
        public uint? current_moodid;
        public string current_music;
        public bool? hasscreened;
        public bool? opt_backdated;
        public bool? opt_nocomments;
        public bool? opt_noemail;
        public bool? opt_preformatted;
        public Screening? opt_screening;
        public string personifi_lang;
        public string personifi_tags;
        public uint? personifi_word_count;
        public string picture_keyword;
        public string qotdid;
        public uint? revnum;
        public uint? revtime;
        public uint? sms_msgid;
        public bool? statusvis;
        public string syn_id;
        public string syn_link;
        public string taglist;
        public bool? unknown8bit;
        public uint? unsuspend_supportid;
        public bool? used_rte;
        public string useragent;
        public string verticals_list;

        public void writeRawData()
        {
            if (rawProps == null) rawProps = new Dictionary<string, string>();
            if (adult_content != null)
                rawProps["prop_adult_content"] = adult_content.ToString().ToLower();
            if (current_coords != null) rawProps["prop_current_coords"] = current_coords;
            if (current_location != null) rawProps["prop_current_location"] = current_location;
            if (current_mood != null) rawProps["prop_current_mood"] = current_mood;
            if (current_moodid != null)
                rawProps["prop_current_moodid"] = current_moodid.ToString();
            if (current_music != null) rawProps["prop_current_music"] = current_music;
            if (opt_backdated == true) rawProps["prop_opt_backdated"] = "1";
            if (opt_nocomments == true) rawProps["prop_opt_nocomments"] = "1";
            if (opt_noemail == true) rawProps["prop_opt_noemail"] = "1";
            if (opt_preformatted == true) rawProps["opt_preformatted"] = "1";
            if (opt_screening != null)
            {
                switch (opt_screening)
                {
                    case Screening.All:
                        rawProps["prop_opt_screening"] = "A";
                        break;
                    case Screening.Anonymous:
                        rawProps["prop_opt_screening"] = "R";
                        break;
                    case Screening.NonFriends:
                        rawProps["prop_opt_screening"] = "F";
                        break;
                    case Screening.None:
                        rawProps["prop_opt_screening"] = "N";
                        break;
                }
            }
            if (picture_keyword != null) rawProps["prop_picture_keyword"] = picture_keyword;
            if (taglist != null) rawProps["prop_taglist"] = taglist;
            if (unknown8bit == true) rawProps["prop_unknown8bit"] = "1";
            if (used_rte == true) rawProps["prop_used_rte"] = "1";
            if (useragent != null) rawProps["prop_useragent"] = useragent;            
        }
        public void parseRawData()
        {
            if (rawProps == null) rawProps = new Dictionary<string, string>();
            foreach (var propName in rawProps.Keys)
            {
                string propValue = rawProps[propName];
                switch (propName)
                {
                    case "admin_content_flag":
                        admin_content_flag = propValue;
                        break;
                    case "adult_content":
                        switch (propValue)
                        {
                            case "none":
                                adult_content = AdultContent.None;
                                break;
                            case "explicit":
                                adult_content = AdultContent.Explicit;
                                break;
                            case "concepts":
                                adult_content = AdultContent.Concepts;
                                break;
                        }
                        break;
                    case "commentalter":
                        commentalter = uint.Parse(propValue);
                        break;
                    case "current_coords":
                        current_coords = propValue;
                        break;
                    case "current_location":
                        current_location = propValue;
                        break;
                    case "current_mood":
                        current_mood = propValue;
                        break;
                    case "current_moodid":
                        current_moodid = uint.Parse(propValue);
                        break;
                    case "current_music":
                        current_music = propValue;
                        break;
                    case "hasscreened":
                        hasscreened = (propValue == "1");
                        break;
                    case "opt_backdated":
                        opt_backdated = (propValue == "1");
                        break;
                    case "opt_nocomments":
                        opt_nocomments = (propValue == "1");
                        break;
                    case "opt_noemail":
                        opt_noemail = (propValue == "1");
                        break;
                    case "opt_preformatted":
                        opt_preformatted = (propValue == "1");
                        break;
                    case "opt_screening":
                        switch (propValue)
                        {
                            case "A":
                                opt_screening = Screening.All;
                                break;
                            case "R":
                                opt_screening = Screening.Anonymous;
                                break;
                            case "F":
                                opt_screening = Screening.NonFriends;
                                break;
                            case "N":
                                opt_screening = Screening.None;
                                break;
                        }
                        break;
                    case "personifi_lang":
                        personifi_lang = propValue;
                        break;
                    case "personifi_tags":
                        personifi_tags = propValue;
                        break;
                    case "personifi_word_count":
                        personifi_word_count = uint.Parse(propValue);
                        break;
                    case "picture_keyword":
                        picture_keyword = propValue;
                        break;
                    case "qotdid":
                        qotdid = propValue;
                        break;
                    case "revnum":
                        revnum = uint.Parse(propValue);
                        break;
                    case "revtime":
                        revtime = uint.Parse(propValue);
                        break;
                    case "sms_msgid":
                        sms_msgid = uint.Parse(propValue);
                        break;
                    case "statusvis":
                        if (propValue == "V") statusvis = true;
                        if (propValue == "S") statusvis = false;
                        break;
                    case "syn_id":
                        syn_id = propValue;
                        break;
                    case "syn_link":
                        syn_link = propValue;
                        break;
                    case "taglist":
                        taglist = propValue;
                        break;
                    case "unknown8bit":
                        unknown8bit = (propValue == "1");
                        break;
                    case "unsuspend_supportid":
                        unsuspend_supportid = uint.Parse(propValue);
                        break;
                    case "used_rte":
                        used_rte = (propValue == "1");
                        break;
                    case "useragent":
                        useragent = propValue;
                        break;
                    case "verticals_list":
                        verticals_list = propValue;
                        break;
                }
            }
        }
    }

    public struct FriendInfo
    {
        public Color backgroundColor;
        public string birthday;
        public Color foregroundColor;
        public FriendStatus friendStatus;
        public FriendType friendType;
        public uint groupMask;
        public string identityDisplay;
        public string identityType;
        public string identityValue;
        public string name;
        public string userName;
    }

    public struct DayCount
    {
        public uint count;
        public DateTime date;
    }

    public struct SyncInfo
    {
        public int itemId;
        public DateTime datetime;
        public string action;
        public string type;
    }

    public enum Security
    {
        Public,
        Private,
        UseMask
    }

    public enum AdultContent
    {
        None,
        Explicit,
        Concepts
    }

    public enum Screening
    {
        All,
        Anonymous,
        NonFriends,
        None
    }

    public enum SelectType
    {
        Day,
        LastN,
        One,
        SyncItems
    }

    public enum FriendType
    {
        Community,
        Normal,
        News,
        Shared,
        Identity
    }

    public enum FriendStatus
    {
        Normal,
        Deleted,
        Suspended,
        Purged
    }

    public class FriendGroupAction
    {
    }

    public class FriendGroupEditMask : FriendGroupAction
    {
        private readonly string _friendName;

        private readonly uint _mask;

        public FriendGroupEditMask(string friendName, uint mask)
        {
            _friendName = friendName;
            _mask = mask;
        }

        public string FriendName
        {
            get { return _friendName; }
        }

        public uint Mask
        {
            get { return _mask; }
        }
    }

    public class FriendGroupDelete : FriendGroupAction
    {
        private readonly uint _friendGroupId;

        public FriendGroupDelete(uint friendGroupId)
        {
            _friendGroupId = friendGroupId;
        }

        public uint FriendGroupId
        {
            get { return _friendGroupId; }
        }
    }

    public class FriendGroupSetName : FriendGroupAction
    {
        private readonly uint _friendGroupId;

        private readonly string _friendGroupName;

        public FriendGroupSetName(uint friendGroupId, string friendGroupName)
        {
            _friendGroupId = friendGroupId;
            _friendGroupName = friendGroupName;
        }

        public uint FriendGroupId
        {
            get { return _friendGroupId; }
        }

        public string FriendGroupName
        {
            get { return _friendGroupName; }
        }
    }

    public class FriendGroupSetSort : FriendGroupAction
    {
        private readonly uint _friendGroupId;

        private readonly uint _sort;

        public FriendGroupSetSort(uint friendGroupId, uint sort)
        {
            _friendGroupId = friendGroupId;
            _sort = sort;
        }

        public uint FriendGroupId
        {
            get { return _friendGroupId; }
        }

        public uint Sort
        {
            get { return _sort; }
        }
    }

    public class FriendGroupSetPublic : FriendGroupAction
    {
        private readonly uint _friendGroupId;

        private readonly bool _public;

        public FriendGroupSetPublic(uint friendGroupId, bool Public)
        {
            _friendGroupId = friendGroupId;
            _public = Public;
        }

        public uint FriendGroupId
        {
            get { return _friendGroupId; }
        }

        public bool Public
        {
            get { return _public; }
        }
    }

    public class LiveJournalException : Exception
    {
        public LiveJournalException(string message) : base(message)
        {
        }
    }
}